//
//  File.swift
//  
//
//  Created by 胡沛远 on 2025/4/2.
//

import Foundation
import SwiftProtobuf


open class UnityAgent<TUnityInterface: Hashable & RawRepresentable, TNativeInterface: Hashable & RawRepresentable>: IDispatcher
where TUnityInterface.RawValue == Int, TNativeInterface.RawValue == Int{
    private let maxOrderCount = 5
    
    private var waiters: [TUnityInterface: [CheckedContinuation<String?, Never>?]] = [:]
    public var handlers: [TNativeInterface: any IHandler] = [:]
    private var sender: ISendMessageToUnity
    
    public init(sender: ISendMessageToUnity) {
        self.sender = sender
    }
    
    public func replyUnity(interfaceCode: TNativeInterface, order: Int, message: Message) {
        let data: Data
        do {
            data = try message.serializedData()
        }catch {
            replyErrorToUnity(interfaceCode: interfaceCode, order: order, code: .unknown)
            return
        }

        replyUnity(interfaceCode: interfaceCode, order: order, withData: data)
    }
    
    public func replyUnity(interfaceCode: TNativeInterface, order: Int, withData message: Data) {
        let b64 = message.base64EncodedString()
        let reply = Reply(apiResultCode: .ok, interfaceCode: interfaceCode.rawValue, order: order, message: b64)
        
        replyUnity(reply: reply)
    }
    
    private func replyUnity(reply: Reply) {
        let jsonData: Data
        do {
            jsonData = try JSONEncoder().encode(reply)
        } catch {
            print("Failed to encode reply")
            return
        }
        
        let jsonString = String(data: jsonData, encoding: .utf8)
        guard case .some(let wrapped) = jsonString else {
            print("Failed to convert data to string")
            return
        }
        
        sender.reply(message: wrapped)
    }
    
    public func replyApiNotFoundToUnity(interfaceCode: Int, order: Int) {
        let reply = Reply(apiResultCode: .notFound, interfaceCode: interfaceCode, order: order, message: "")
        
        replyUnity(reply: reply)
    }
    
    public func replyErrorToUnity(interfaceCode: TNativeInterface, order: Int, code: ApiResultCode) {
        if code == .ok {
            print("Can not return Ok from this interface")
            return
        }
        
        let reply = Reply(apiResultCode: code, interfaceCode: interfaceCode.rawValue, order: order, message: "")
        
        replyUnity(reply: reply)
    }
    
    public func dispatch(ask: Ask) {
        let order = ask.order
        
        guard case let .some(e) = TNativeInterface(rawValue: ask.interfaceCode) else {
            replyApiNotFoundToUnity(interfaceCode: ask.interfaceCode, order: order)
            return
        }
        
        guard let h = handlers[e] else {
            replyApiNotFoundToUnity(interfaceCode: ask.interfaceCode, order: order)
            return
        }
        
        
        Task {
            let result = await h.handle(order: order, message: ask.message)
            
            switch result {
            case let .ok(data):
                replyUnity(interfaceCode: e, order: order, withData: data)
            case let .error(err):
                replyErrorToUnity(interfaceCode: e, order: order, code: err)
            }
        }
    }
    
    private func askUnity(ask: Ask){
        let jsonData: Data
        do {
            jsonData = try JSONEncoder().encode(ask)
        } catch {
            print("Failed to encode reply")
            return
        }
        
        let jsonString = String(data: jsonData, encoding: .utf8)
        guard case .some(let wrapped) = jsonString else {
            print("Failed to convert data to string")
            return
        }
        
        sender.ask(message: wrapped)
    }
    
    func askUnity(interfaceCode: TUnityInterface, order: Int, message: Message) {
        let b64: String
        do {
            b64 = try message.serializedData().base64EncodedString()
        } catch {
            print("Failed to convert message to base64")
            return
        }
        
        let ask = Ask(interfaceCode: interfaceCode.rawValue, order: order, message: b64)
        
        askUnity(ask: ask)
    }
    
    func enqueue(interfaceCode: TUnityInterface, continuation: CheckedContinuation<String?, Never>) -> Int? {
        let e = interfaceCode
        var arr: [CheckedContinuation<String?, Never>?]
        
        if waiters.keys.contains(e) {
            arr = waiters[e]!
        } else {
            arr = [CheckedContinuation<String?, Never>?](repeating: nil, count: maxOrderCount)
            waiters[e] = arr
        }
        
        for i in 0..<arr.count {
            if arr[i] == nil {
                arr[i] = continuation
                return i
            }
        }
        
        print("Failed to wait \(interfaceCode), too many waiters")
        return nil
    }
    
    public func notify(reply: Reply) {
        guard case let .some(e) = TUnityInterface(rawValue: reply.interfaceCode) else {
            print("Got an not surpported interface code: \(reply.interfaceCode)")
            return
        }
        
        let order = Int(reply.order)
        
        if order < 0 {
            print("Got an invalid order: \(order), interface: \(reply.interfaceCode)")
            return
        }
        
        if let arr = waiters[e] {
            if order >= arr.count {
                print("Got an too big order: \(order), interface: \(reply.interfaceCode)")
                return
            }
            
            if let c = arr[order] {
                var newArr = arr
                newArr[order] = nil
                waiters[e] = newArr
                
                if reply.apiResultCode == .ok {
                    c.resume(returning: reply.message)
                } else {
                    print("Got an error for interface: \(reply.interfaceCode): code: \(reply.apiResultCode), order \(order)")
                    c.resume(returning: nil)
                }
            } else {
                print("Not waiter for interface: \(reply.interfaceCode): order \(order)")
            }
        } else {
            print("Native never asked \(reply.interfaceCode)")
        }
    }
}
